package foo.bar.wiki.services;

import org.springframework.orm.jpa.JpaTemplate;
import org.springframework.orm.jpa.JpaCallback;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import foo.bar.wiki.web.action.ExtendedPaginatedList;
import foo.bar.wiki.web.action.PaginatedBaseActionBean;
import foo.bar.wiki.domain.*;
import foo.bar.wiki.services.exception.*;
import foo.bar.wiki.security.UserAccount;
import foo.bar.wiki.security.UserAccountRealm;
import foo.bar.wiki.plugins.PluginManager;
import foo.bar.wiki.plugins.PluginException;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceException;
import javax.persistence.Query;
import java.util.Iterator;
import java.util.ArrayList;
import java.util.List;

/**
 * @author tmjee
 * @version $Date$ $Id$
 */
public class WikiService extends AbstractJpaAwareService {

    private static final Log LOG = LogFactory.getLog(WikiService.class);

    private PluginManager pluginManager;

    WikiService() {}
    public WikiService(JpaTemplate template, PluginManager pluginManager) {
        super(template);
        this.pluginManager = pluginManager;
    }

    public ExtendedPaginatedList getSpaces(final ExtendedPaginatedList spaces) {
        return (ExtendedPaginatedList) getJpaTemplate().execute(new JpaCallback(){
            public Object doInJpa(EntityManager entityManager) throws PersistenceException {
                Query q1 = entityManager.createNamedQuery("Space_findAllSpaces");
                prepareQueryWithPaginatedListInfo(spaces, q1);
                Query q2 = entityManager.createNamedQuery("Space_countAllSpaces");
                long total = (Long) getFirstResult(q2);
                return populatePagniatedList(spaces, q1.getResultList(), total);
            }
        });
    }

    public Space getSpaceById(final String id) {
        return (Space) getJpaTemplate().execute(new JpaCallback(){
            public Object doInJpa(EntityManager entityManager) throws PersistenceException {
                Query q = entityManager.createNamedQuery("Space_findSpaceById");
                q.setParameter("spaceId", id);
                return getFirstResult(q);
            }
        });
    }

    public Space saveSpace(final Space space) throws SpaceAlreadyExistsException, PluginException {
        pluginManager.getSpaceListenerPlugin().preSpaceCreation(space);
        return (Space) getJpaTemplate().execute(new JpaCallback(){
            public Object doInJpa(EntityManager entityManager) throws PersistenceException {
                Query q = entityManager.createNamedQuery("Space_findSpaceByName");
                q.setParameter("spaceName", space.getName());
                if (getFirstResult(q) != null) {
                    throw new SpaceAlreadyExistsException("Space with name ["+space.getName()+"] already exists");
                }
                Space spaceCreated = (Space) saveOrUpdate(entityManager, space);
                pluginManager.getSpaceListenerPlugin().postSpaceCreation(spaceCreated);
                return spaceCreated;
            }
        });
    }

    public Space updateSpace(final Space space) throws NoSuchSpaceException, PluginException {
        pluginManager.getSpaceListenerPlugin().preSpaceUpdate(space);
        return (Space) getJpaTemplate().execute(new JpaCallback() {
            public Object doInJpa(EntityManager entityManager) throws PersistenceException {
                Query q = entityManager.createNamedQuery("Space_findSpaceByName");
                q.setParameter("spaceName", space.getName());
                if (getFirstResult(q) == null) {
                    throw new NoSuchSpaceException("Space with name ["+space.getName()+"] already exists");    
                }
                Space spaceUpdated = (Space) saveOrUpdate(entityManager, space);
                pluginManager.getSpaceListenerPlugin().postSpaceUpdate(space);
                return spaceUpdated;
            }
        });
    }

    public void removeSpace(final Space space) throws PluginException {
        pluginManager.getSpaceListenerPlugin().preSpaceRemoval(space);
        getJpaTemplate().execute(new JpaCallback(){
            public Object doInJpa(EntityManager entityManager) throws PersistenceException {
                entityManager.remove(space);
                Query q = entityManager.createNamedQuery("Page_findPagesBySpaceId");
                q.setParameter("spaceId", space.getId());
                for (Iterator i = q.getResultList().iterator(); i.hasNext(); ) {
                    Page page = (Page) i.next();
                    entityManager.remove(page);
                }
                pluginManager.getSpaceListenerPlugin().postSpaceRemoval(space);
                return null;
            }
        });
    }

    public ExtendedPaginatedList getSpaceMembers(final Space space, final ExtendedPaginatedList members) {
        return (ExtendedPaginatedList) getJpaTemplate().execute(new JpaCallback(){
            public Object doInJpa(EntityManager entityManager) throws PersistenceException {

                Query q1 = entityManager.createNamedQuery("Space_findSpaceMembers");
                prepareQueryWithPaginatedListInfo(members, q1);
                q1.setParameter("spaceId", space.getId());

                Query q2 = entityManager.createNamedQuery("Space_countSpaceMembers");
                q2.setParameter("spaceId", space.getId());
                long total = (Long) getFirstResult(q2);

                return populatePagniatedList(members,  q1.getResultList(), total);
            }
        });
    }

    public ExtendedPaginatedList getSpaceNonMembers(final Space space, final ExtendedPaginatedList nonMembers) {
        return (ExtendedPaginatedList) getJpaTemplate().execute(new JpaCallback(){
            public Object doInJpa(EntityManager entityManager) throws PersistenceException {

                Query q = entityManager.createNamedQuery("Space_findSpaceMemberIds");
                q.setParameter("spaceId", space.getId());

                Query q1 = entityManager.createNamedQuery("Space_findNonSpaceMembers");
                prepareQueryWithPaginatedListInfo(nonMembers, q1);
                q1.setParameter("spaceId", space.getId());
                q1.setParameter("memberIds", q.getResultList());


                Query q2 = entityManager.createNamedQuery("Space_countNonSpaceMembers");
                q2.setParameter("spaceId", space.getId());
                q2.setParameter("memberIds", q.getResultList());
                long total = (Long) getFirstResult(q2);

                return populatePagniatedList(nonMembers,  q1.getResultList(), total);
            }    
        });
    }

    public ExtendedPaginatedList getSpacePages(final Space space, final ExtendedPaginatedList pages) {
        return (ExtendedPaginatedList) getJpaTemplate().execute(new JpaCallback(){
            public Object doInJpa(EntityManager entityManager) throws PersistenceException {

                Query q1 = entityManager.createNamedQuery("Page_findPagesBySpaceId");
                q1.setParameter("spaceId", space.getId());
                prepareQueryWithPaginatedListInfo(pages, q1);

                Query q2 = entityManager.createNamedQuery("Page_countPagesBySpaceId");
                q2.setParameter("spaceId", space.getId());
                long total = (Long) getFirstResult(q2);

                return populatePagniatedList(pages, q1.getResultList(), total);
            }
        });
    }

    public Page getPageById(final String id) {
        return (Page) getJpaTemplate().execute(new JpaCallback(){
            public Object doInJpa(EntityManager entityManager) throws PersistenceException {
                Query q = entityManager.createNamedQuery("Page_findPageById");
                q.setParameter("pageId", id);
                return getFirstResult(q);
            }
        });
    }

    public Page savePage(final Page page) throws PageAlreadyExistsException, PluginException {
        pluginManager.getPageListenerPlugin().prePageCreation(page);
        return (Page) getJpaTemplate().execute(new JpaCallback(){
            public Object doInJpa(EntityManager entityManager) throws PersistenceException {
                Query q1 = entityManager.createNamedQuery("Page_findPageBySpaceIdAndPageName");
                q1.setParameter("pageName", page.getName());
                q1.setParameter("spaceId", page.getSpace().getId());
                if (getFirstResult(q1) != null)
                    throw new PageAlreadyExistsException("Page ["+page.getName()+"] already exists in space ["+page.getSpace().getName()+"]");
                Page pageCreated = (Page) saveOrUpdate(entityManager, page);
                pluginManager.getPageListenerPlugin().postPageCreation(pageCreated);
                return pageCreated;
            }
        });
    }

    public Page updatePage(final Page page) throws NoSuchPageException, PluginException {
        pluginManager.getPageListenerPlugin().prePageUpdate(page);
        return (Page) getJpaTemplate().execute(new JpaCallback(){
            public Object doInJpa(EntityManager entityManager) throws PersistenceException {
                Query q1= entityManager.createNamedQuery("Page_findPageBySpaceIdAndPageName");
                q1.setParameter("pageName", page.getName());
                q1.setParameter("spaceId", page.getSpace().getId());
                if (getFirstResult(q1) == null) {
                    throw new NoSuchPageException("Page ["+page.getName()+"] doesn't exists");
                }
                Page pageUpdated = (Page) saveOrUpdate(entityManager, page);
                pluginManager.getPageListenerPlugin().postPageUpdate(pageUpdated);
                return pageUpdated;
            }
        });
    }

    public void removePage(final Page page) throws PluginException {
        pluginManager.getPageListenerPlugin().prePageRemoval(page);
        getJpaTemplate().execute(new JpaCallback(){
            public Object doInJpa(EntityManager entityManager) throws PersistenceException {
                entityManager.remove(page);
                pluginManager.getPageListenerPlugin().postPageRemoval(page);
                return null;
            }
        });
    }

    public ExtendedPaginatedList getRecentlyUpdatedPages(final ExtendedPaginatedList recentUpdatedPages) {
        return (ExtendedPaginatedList) getJpaTemplate().execute(new JpaCallback(){
            public Object doInJpa(EntityManager entityManager) throws PersistenceException {
                Query q1 = entityManager.createNamedQuery("Page_findRecentlyUpdatedPages");
                prepareQueryWithPaginatedListInfo(recentUpdatedPages, q1);

                Query q2 = entityManager.createNamedQuery("Page_countRecentlyUpdatedPages");
                long total = (Long) getFirstResult(q2);
                return populatePagniatedList(recentUpdatedPages, q1.getResultList(), total);
            }
        });
    }

    public ExtendedPaginatedList getFavouritePages(final User currentUser, final ExtendedPaginatedList favouritePages) {
        return (ExtendedPaginatedList) getJpaTemplate().execute(new JpaCallback() {
            public Object doInJpa(EntityManager entityManager) throws PersistenceException {
                if (UserAccountRealm.ANONYMOUS_USER_ACCOUNT.getUser().equals(currentUser)) {
                    // anonymous user, no favourite pages
                    return favouritePages;
                }
                else {

                    Query q1 = entityManager.createNamedQuery("User_findFavouritePages");
                    q1.setParameter("user", currentUser);
                    prepareQueryWithPaginatedListInfo(favouritePages, q1);

                    Query q2 = entityManager.createNamedQuery("User_countFavouritePages");
                    q2.setParameter("user", currentUser);
                    long total = (Long) getFirstResult(q2);

                    return populatePagniatedList(favouritePages, q1.getResultList(), total);
                }
            }
        });
    }

    public Page getSpacePageByName(final String spaceId, final String pageName) {
        return (Page) getJpaTemplate().execute(new JpaCallback(){
            public Object doInJpa(EntityManager em) throws PersistenceException {
                Query q = em.createNamedQuery("Page_findPageBySpaceIdAndPageName");
                q.setParameter("spaceId", spaceId);
                q.setParameter("pageName", pageName);
                return getFirstResult(q);
            }
        });
    }

    public Resource getResourceById(final String id) {
        return (Resource) getJpaTemplate().execute(new JpaCallback() {
            public Object doInJpa(EntityManager em) throws PersistenceException {
                Query q = em.createNamedQuery("Resource_findResourceById");
                q.setParameter("resourceId", id);
                return getFirstResult(q);
            }
        });
    }

    public void removeResource(final Resource resource) {
        getJpaTemplate().execute(new JpaCallback(){
            public Object doInJpa(EntityManager em) throws PersistenceException {
                em.remove(resource);
                return null;
            }
        });
    }

    public Resource updateResource(final Resource resource) throws NoSuchResourceException  {
        return (Resource) getJpaTemplate().execute(new JpaCallback(){
            public Object doInJpa(EntityManager em) throws PersistenceException {
                Query q = em.createNamedQuery("Resource_findResourceById");
                q.setParameter("resourceId", resource.getId());
                if (getFirstResult(q) == null) {
                    throw new NoSuchResourceException("No such resource ["+resource.getId()+"] available cannot update");
                }
                return saveOrUpdate(em, resource);
            }
        });
    }

    public Resource saveResource(final Resource resource) throws ResourceAlreadyExistsException {
        return (Resource) getJpaTemplate().execute(new JpaCallback(){
            public Object doInJpa(EntityManager em) throws PersistenceException {
                Query q = em.createNamedQuery("Resource_findResourceByName");
                q.setParameter("resourceName", resource.getName());
                if (getFirstResult(q) != null) {
                    throw new ResourceAlreadyExistsException("Resource ["+resource.getName()+"] already exists, cannot save");
                }
                //return saveOrUpdate(em, resource);
                return resource;
            }
        });
    }

    public Identifiable saveOrUpdate(final Identifiable identifiable) {
        return (Identifiable) getJpaTemplate().execute(new JpaCallback(){
            public Object doInJpa(EntityManager em) throws PersistenceException {
                return saveOrUpdate(em, identifiable);
            }
        });
    }

    public Comment getCommentById(final String id) {
        return (Comment) getJpaTemplate().execute(new JpaCallback(){
            public Object doInJpa(EntityManager em) throws PersistenceException {
                Query q = em.createNamedQuery("Comment_findCommentById");
                q.setParameter("commentId", id);
                return getFirstResult(q);
            }
        });
    }

    public void removeComment(final Comment comment)throws PluginException {
        pluginManager.getCommentListenerPlugin().preCommentRemoval(comment);
        getJpaTemplate().execute(new JpaCallback(){
            public Object doInJpa(EntityManager em) throws PersistenceException {
                em.remove(comment);
                pluginManager.getCommentListenerPlugin().postCommentRemoval(comment);
                return null;
            }
        });
    }

    public Comment saveComment(final Comment comment) throws PluginException {
        pluginManager.getCommentListenerPlugin().preCommentCreation(comment);
        return (Comment) getJpaTemplate().execute(new JpaCallback(){
            public Object doInJpa(EntityManager em) throws PersistenceException {
                Comment commentCreated = (Comment) saveOrUpdate(em, comment);
                pluginManager.getCommentListenerPlugin().postCommentCreation(commentCreated);    
                return commentCreated;
            }
        });
    }

    public Resource getResourceByNameAndSpaceId(final String name, final String spaceId) {
        return (Resource) getJpaTemplate().execute(new JpaCallback(){
            public Object doInJpa(EntityManager em) throws PersistenceException {
                Query q = em.createNamedQuery("Resource_findResourceByNameAndId");
                q.setParameter("resourceName", name);
                q.setParameter("spaceId", spaceId);
                return getFirstResult(q);
            }
        });
    }

    public void markPageAsSpaceDefault(Page page) throws PluginException {
        Space space = getSpaceById(page.getSpace().getId());
        space.setDefaultPage(page);
        updateSpace(space);
    }


    public void resetSpaceDefaultPage(Space space) throws PluginException {
        space.setDefaultPage(null);
        updateSpace(space);
    }
}
